package spring.factory;

import spring.aop.Advice;
import spring.aop.AopConfig;
import spring.transaction.TransactionBeanFactory;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class DefaultListableBeanFactory implements SingletonBeanRegistry, TransactionBeanFactory {

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    private final Map<String, Advice> txAdviceObjects = new ConcurrentHashMap<>(8);

    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

    private final Set<String> singletonsCurrentlyInCreation =
            Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    private final Set<String> transScopes = Collections.newSetFromMap(new ConcurrentHashMap<>(128));

    private AopConfig aopConfig;

    @Override
    public <T> T getBean(String beanId) {
        //noinspection unchecked
        return (T) this.singletonObjects.get(beanId);
    }

    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            Object oldObject = this.singletonObjects.get(beanName);
            if (oldObject != null) {
                throw new IllegalStateException("Could not register object [" + singletonObject +
                        "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
            }
            addSingleton(beanName, singletonObject);
        }
    }

    private void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
        }
    }

    @Override
    public void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
            }
        }
    }

    @Override
    public Object getSingleton(String beanName) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null) {
                synchronized (this.singletonObjects) {
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }

    @Override
    public Object getSingletonByType(String clsName) {
        for (Object object : this.singletonObjects.values()) {
            if (object.getClass().getName().equals(clsName)) {
                return object;
            }
        }
        return null;
    }

    public boolean isSingletonCurrentlyInCreation(String beanName) {
        return this.singletonsCurrentlyInCreation.contains(beanName);
    }

    @Override
    public boolean containsSingleton(String beanName) {
        return singletonObjects.containsKey(beanName);
    }

    @Override
    public void removeSingleton(String beanName) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.remove(beanName);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
        }
    }

    @Override
    public void registerTransactionPointCut(String name) {
        transScopes.add(name);
    }

    @Override
    public boolean beginTransactionIfNecessary(String name) {
        if (transScopes.isEmpty()) {
            return false;
        }
        return transScopes.contains(name);
    }

    @Override
    public void registerAdvice(String id, Advice advice) {
        this.txAdviceObjects.put(id, advice);
    }

    @Override
    public Advice getAdvice(String id) {
        return this.txAdviceObjects.get(id);
    }

    @Override
    public void registerAopConfig(AopConfig aopConfig) {
        this.aopConfig = aopConfig;
    }

    @Override
    public AopConfig getAopConfig() {
        return this.aopConfig;
    }

    @Override
    public boolean isAnnotationBeanFactory() {
        return !transScopes.isEmpty();
    }
}
